// SPDX-License-Identifier: GPL-2.0+
/*
 * Copyright (C) 2019 TangHaifeng <tang-haifeng@foxmail.com>
 *
 */

#include <common.h>
#include <dm.h>
#include <errno.h>
#include <fdtdec.h>
#include <malloc.h>
#include <linux/io.h>
#include <asm/io.h>
#include <asm/gpio.h>
#include <dm/device-internal.h>
#include <dt-bindings/gpio/gpio.h>

#define LS7A_PCH_REG_BASE		0x10000000
/* CHIPCFG regs */
#define LS7A_CHIPCFG_REG_BASE 	(LS7A_PCH_REG_BASE + 0x0a000000)
#define LS7A_PCIE_BAR_BASE(bus, dev, func) \
	readl((void *)CKSEG1ADDR(LS7A_CHIPCFG_REG_BASE | (bus << 16) | (dev << 11) | (func << 8) | 0x10))
/* 7A bridge has a gpio controller in DC space */
#define LS7A_DC_CNT_REG_BASE	(LS7A_PCIE_BAR_BASE(0x0, 0x6, 0x1) & 0xfffffff0)

#define GPIO_DIR		0x10
#define GPIO_OUTPUT		0x00
#define GPIO_DATA		0x00

struct ls7a_dc_gpio_platdata {
	void __iomem *base;
	char bank_name[32];	/* Name of bank */
	int gpio_count;
};

static int ls7a_dc_gpio_get_value(struct udevice *dev, unsigned int offset)
{
	struct ls7a_dc_gpio_platdata *plat = dev_get_priv(dev);

	return !!(ioread32(plat->base + GPIO_DATA) & BIT(offset));
}

static int ls7a_dc_gpio_set_value(struct udevice *dev, unsigned int offset,
				   int value)
{
	struct ls7a_dc_gpio_platdata *plat = dev_get_priv(dev);

	if (value)
		setbits_le32(plat->base + GPIO_OUTPUT,
		     BIT(offset));
	else
		clrbits_le32(plat->base + GPIO_OUTPUT,
		     BIT(offset));

	return 0;
}

static int ls7a_dc_gpio_direction_input(struct udevice *dev, unsigned int offset)
{
	struct ls7a_dc_gpio_platdata *plat = dev_get_priv(dev);

	setbits_le32(plat->base + GPIO_DIR,
		     BIT(offset));

	return 0;
}

static int ls7a_dc_gpio_direction_output(struct udevice *dev, unsigned int offset,
					  int value)
{
	struct ls7a_dc_gpio_platdata *plat = dev_get_priv(dev);

	clrbits_le32(plat->base + GPIO_DIR,
		     BIT(offset));
	ls7a_dc_gpio_set_value(dev, offset, value);

	return 0;
}

static const struct dm_gpio_ops gpio_ls7a_dc_ops = {
	.direction_input	= ls7a_dc_gpio_direction_input,
	.direction_output	= ls7a_dc_gpio_direction_output,
	.get_value		= ls7a_dc_gpio_get_value,
	.set_value		= ls7a_dc_gpio_set_value,
};

static int gpio_ls7a_dc_probe(struct udevice *dev)
{
	struct gpio_dev_priv *uc_priv = dev_get_uclass_priv(dev);
	struct ls7a_dc_gpio_platdata *plat = dev_get_priv(dev);

	plat->base = (void __iomem *)(ulong)(LS7A_DC_CNT_REG_BASE) + 0x1650;
	if (!plat->base)
		return -EINVAL;

	snprintf(plat->bank_name, sizeof(plat->bank_name), "gpio@%x", (u32)((ulong)plat->base));
	plat->gpio_count = 4;

	uc_priv->gpio_count = plat->gpio_count;
	uc_priv->bank_name = plat->bank_name;

	return 0;
}

static const struct udevice_id ls7a_dc_gpio_ids[] = {
	{ .compatible = "loongson,ls7a-dc-gpio" },
	{ }
};

U_BOOT_DRIVER(gpio_ls7a_dc) = {
	.name	= "gpio_ls7a_dc",
	.id	= UCLASS_GPIO,
	.ops	= &gpio_ls7a_dc_ops,
	.of_match = ls7a_dc_gpio_ids,
	.priv_auto_alloc_size = sizeof(struct ls7a_dc_gpio_platdata),
	.probe	= gpio_ls7a_dc_probe,
};
